package org.example.socket;

import com.google.gson.Gson;
import com.sun.istack.internal.Nullable;
import org.example.socket.pojo.Request;
import org.example.socket.pojo.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;

public class Server {

    // 实现socket服务启动
    public void start(int port) {
        // 1.创建socket服务
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            serverSocket.setSoTimeout(120000);

            // 2.等待客户端连接
            System.out.println("Server start at port " + port);
            System.out.println("waiting for client connecting...");
            // 循环处理客户端连接
            while (true) {
                Socket socket = serverSocket.accept();
                // 在新线程中处理每个客户端连接
                ServerHandler serverHandler = new ServerHandler(socket);
                serverHandler.run();
            }
        } catch (IOException e) {
            System.out.println("Server run error");
            e.printStackTrace();
        }
    }
}

// 处理socket文本，按http协议进行分割，获取请求头和请求体
class ServerHandler implements Runnable {
    private final Socket socket;
    final Logger logger = LoggerFactory.getLogger(ServerHandler.class);

    public ServerHandler(Socket socket) throws SocketException {
        this.socket = socket;
        socket.setReceiveBufferSize(1024 * 1024 * 500); // 限制为500MB
        System.out.println("--------------------------------------------------------------------------------");
        System.out.println("Client connected on " + socket.getRemoteSocketAddress());
    }

    @Override
    public void run() {
        // 这里有个大坑，一旦使用try-with-resource，就必须把输入流和输出流以及基于引用两者的对象都放在try-with-resource中，集体关闭
        // 否则如果不集中管理，可能就会导致socket意外关闭，导致后面无法获取到请求头和请求体
        // 例子：当 BufferedReader bufferedReader 放在getRequest方法中，并且用try-with-resource，
        // 那么当getRequest方法的try-with-resource走完结束后，就会释放bufferedReader和其引用的输入流，导致后面提前直接断开socket连接

        try (
                InputStream inputStream = socket.getInputStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                OutputStream outputStream = socket.getOutputStream();
        ) {
            Request request = getRequest(bufferedReader);
            if (request == null) {
                throw new IOException("Invalid request");
            }
            RequestHandler requestHandler = new RequestHandler(socket, request);
            requestHandler.handleRequest(outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close();
            System.out.println("Client disconnected on " + socket.getRemoteSocketAddress());
            System.out.println("--------------------------------------------------------------------------------");
            System.out.println();
        }
    }

    // 关闭
    public void close() {
        try {
            socket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Request getRequest(BufferedReader bufferedReader) {
        try {
            StringBuilder httpContent = new StringBuilder();
            String line = "";
            while (!(line = bufferedReader.readLine()).isEmpty()) {
                httpContent.append(line).append("\r\n");
            }

            Request request = new Request(httpContent.toString());

            logger.info("Request: \n{}", httpContent);

            // 并返回request对象
            return request;

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

// 处理请求
class RequestHandler {
    private final Socket socket;
    private final Request request;

    private final Path ROOTDIR = Paths.get(System.getProperty("user.dir"));

    // 模块名改了要改
    private final Path RELATIVE_ASSET_DIR = Paths.get("Tomcat_and_Servlet/src/main/assets");
    private final Path ASSET_DIR = Paths.get(ROOTDIR.resolve(RELATIVE_ASSET_DIR.toString()).toString());

    public RequestHandler(Socket socket, Request request) {
        this.socket = socket;
        this.request = request;
    }

    // 生成body为灰黑色的html片段
    public String generateBody(String html) {
        return "<body style=\"background: black; width: 100%; height: 100%; white-space: pre; color: white; margin: 0; padding: 24px; box-sizing: border-box;\">" + html + "</body>";
    }

    public void handleFile(String path, OutputStream out, Boolean downloadAlways) throws IOException {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        Path filePath = ASSET_DIR.resolve(Paths.get(path));
        String filename = path.substring(path.lastIndexOf("/") + 1);
        long size = Files.size(filePath);
        HashMap<String, String> headers = new HashMap<>();

        // 修改文件请求头
        ContentTypeHelper.initFileHeaders(headers, filename, downloadAlways);

        // 处理文件
        handleFileStream(filePath, size, out, headers);
    }

    public void handleFileStream(Path filePath, long size, OutputStream out, HashMap<String, String> headers) throws IOException {
        HashMap<String, String> requestHeaders = request.getHeaders();
        boolean isRange = requestHeaders.containsKey("Range");
        long start = 0;
        long end = size - 1;

        if (isRange) {
            // 处理断点续传
            String range = requestHeaders.get("Range");
            String[] rangeTemp = range.split("=");
            String[] rangeValue = rangeTemp[1].split("-");
            start = Long.parseLong(rangeValue[0]);
            if (rangeValue.length == 1) {
                end = size - 1;
            } else {
                end = Long.parseLong(rangeValue[1]);
            }

            if (start > size - 1) {
                start = 0;
            }
            if (end > size - 1) {
                end = size - 1;
            }

            headers.put("Content-Length", String.valueOf(end - start + 1));
            headers.put("Content-Range", "bytes " + start + "-" + end + "/" + size);

            Response response = new Response(206, headers, "");
            out.write(response.toResponseString().getBytes());

            // 流读取文件，直接定位start-end
            try (
                    FileInputStream fileInputStream = new FileInputStream(filePath.toString());
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
            ) {
                byte[] buffer = new byte[1024];
                int length;
                bufferedInputStream.skip(start);
                while ((length = bufferedInputStream.read(buffer)) != -1) {
                    // 从缓冲区读取数据
                    Boolean result = handleErrorWrite(out, buffer, 0, length);
                    if (!result) {
                        break;
                    }
                }
            }
            out.flush();
        } else {
            headers.put("Content-Length", String.valueOf(end - start + 1));
            Response response = new Response(200, headers, "");

            // 读取文件：方式2
            byte[] buffer = Files.readAllBytes(filePath);

            byte[] headerBytes = response.toResponseString().getBytes(StandardCharsets.UTF_8);
            byte[] combinedBytes = new byte[headerBytes.length + buffer.length];
            System.arraycopy(headerBytes, 0, combinedBytes, 0, headerBytes.length);
            System.arraycopy(buffer, 0, combinedBytes, headerBytes.length, buffer.length);
            handleErrorWrite(out, combinedBytes, 0, combinedBytes.length);
        }
    }

    private Boolean handleErrorWrite(OutputStream out, byte[] data, int offset, int length) throws IOException {
        try {
            out.write(data, offset, length);
            return true;
        } catch (IOException e) {
            if (e.getMessage().contains("Connection reset by peer")) {
                System.out.println("客户端已断开连接");
                System.out.println();
                return false;
            } else {
                throw e;
            }
        }
    }


    public boolean hasFile(String path) {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        String filePath = ASSET_DIR.resolve(Paths.get(path)).toString();
        // 如果是目录也返回false
        return Files.exists(Paths.get(filePath)) && !Files.isDirectory(Paths.get(filePath));
    }

    // 处理请求
    public void handleRequest(OutputStream outputStream) throws IOException {
        String method = request.getMethod();
        String path = request.getPath();
        String body = request.getBody();
        HashMap<String, String> headers = new HashMap<>();
        HashMap<String, Object> query = request.getQuery();

        headers.put("Content-Type", "text/html;charset=utf-8");
        headers.put("Connection", "keep-alive");

        // 转化request为json
        Gson gson = new Gson();
        String reqJson = gson.toJson(request);

        Response response = null;

        switch (method) {
            case "GET":
                if (path.equals("/")) {
                    response = new Response(200, headers, generateBody("<h1>Hello World</h1>"));
                } else if (path.contains("/request-info")) {
                    response = new Response(200, headers, generateBody("<p>" + reqJson + "</p>"));
                } else {
                    path = URLDecoder.decode(path, "UTF-8");

                    if (!hasFile(path)) {
                        response = new Response(404, headers, generateBody("<h1>404 Not Found</h1>"));
                    }
                    // 读取文件
                    else {
                        try {
                            // 通过控制query的download来控制下载
                            handleFile(path, outputStream, query.get("download") != null);
                            return;
                        } catch (IOException e) {
                            e.printStackTrace();
                            response = new Response(404, headers, generateBody("<h1>500 Server run error</h1>"));
                        }
                    }
                }
                break;
            case "POST":
                if (path.equals("/")) {
                    response = new Response(200, headers, generateBody("<h1>Hello World</h1>"));
                } else if (path.contains("/request-info")) {
                    response = new Response(200, headers, generateBody("<p>" + reqJson + "</p>"));
                } else {
                    response = new Response(404, headers, generateBody("<h1>404 Not Found</h1>"));
                }
                break;
            case "OPTIONS":
                response = new Response(200, headers, "OK");
                break;
            default:
                response = new Response(405, headers, "不支持的请求方法");
        }


        if (socket.isClosed()) {
            // 转化为字节流
            outputStream.write(response.toResponseString().getBytes());
            // 刷新输出流，确保数据立即发送
            outputStream.flush();
        }
    }
}


class ContentTypeHelper {

    private static final HashMap<String, String> MIME_TYPE_MAP = new HashMap<>();

    static {
        // 初始化映射表
        MIME_TYPE_MAP.put("txt", "text/plain");
        MIME_TYPE_MAP.put("html", "text/html");
        MIME_TYPE_MAP.put("css", "text/css");
        MIME_TYPE_MAP.put("js", "application/javascript");
        MIME_TYPE_MAP.put("json", "application/json");
        MIME_TYPE_MAP.put("xml", "application/xml");
        MIME_TYPE_MAP.put("jpg", "image/jpeg");
        MIME_TYPE_MAP.put("jpeg", "image/jpeg");
        MIME_TYPE_MAP.put("png", "image/png");
        MIME_TYPE_MAP.put("gif", "image/gif");
        MIME_TYPE_MAP.put("bmp", "image/bmp");
        MIME_TYPE_MAP.put("ico", "image/vnd.microsoft.icon");
        MIME_TYPE_MAP.put("svg", "image/svg+xml");
        MIME_TYPE_MAP.put("pdf", "application/pdf");
        MIME_TYPE_MAP.put("doc", "application/msword");
        MIME_TYPE_MAP.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        MIME_TYPE_MAP.put("xls", "application/vnd.ms-excel");
        MIME_TYPE_MAP.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        MIME_TYPE_MAP.put("ppt", "application/vnd.ms-powerpoint");
        MIME_TYPE_MAP.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
        MIME_TYPE_MAP.put("zip", "application/zip");
        MIME_TYPE_MAP.put("gz", "application/gzip");
        MIME_TYPE_MAP.put("tgz", "application/x-compressed-tar");
        MIME_TYPE_MAP.put("tar", "application/x-tar");
        MIME_TYPE_MAP.put("bz2", "application/x-bzip2");
        MIME_TYPE_MAP.put("mp3", "audio/mpeg");
        MIME_TYPE_MAP.put("wav", "audio/wav");
        MIME_TYPE_MAP.put("ogg", "audio/ogg");
        MIME_TYPE_MAP.put("mp4", "video/mp4");
        MIME_TYPE_MAP.put("avi", "video/x-msvideo");
        MIME_TYPE_MAP.put("mkv", "video/x-matroska");
        MIME_TYPE_MAP.put("webm", "video/webm");
    }

    /**
     * 根据文件扩展名获取对应的Content-Type（MIME类型）。
     *
     * @param filename 文件扩展名（不含点，如"txt"、"jpg"等）
     * @return 对应的Content-Type，如果没有匹配项，则返回默认值 "application/octet-stream"
     */
    public static String getContentTypeForExtension(String filename) {
        // 获取拓展名
        String extension = filename.substring(filename.lastIndexOf(".") + 1);
        return MIME_TYPE_MAP.getOrDefault(extension.toLowerCase(), "application/octet-stream");
    }

    public static void initFileHeaders(HashMap<String, String> headers, String filename, Boolean downloadAlways) {
        headers.put("Content-Type", ContentTypeHelper.getContentTypeForExtension(filename));
        // 设置Content-Disposition
        headers.put("Content-Disposition", (downloadAlways ? "attachment;" : "inline;") + "filename=\"" + filename + "\";");
    }
}